﻿using Bootstrap.Security.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;

namespace Microsoft.AspNetCore.Builder
{
    /// <summary>
    /// BootstrapAdminAuthorization 认证服务扩展类
    /// </summary>
    public static class BootstrapAdminAuthenticationExtensions
    {
        private static Func<string, IEnumerable<string>> _urlAuthHandler;
        private static Func<string, IEnumerable<string>> _appAuthHandler;
        private static Func<string, IEnumerable<string>> _userAuthHandler;

        /// <summary>
        /// 添加 BootstrapAdmin 认证授权服务，内部调用 UseAuthentication
        /// </summary>
        /// <param name="builder">IApplicationBuilder 实例</param>
        /// <param name="appId">应用程序 ID</param>
        /// <param name="userNameAuthHandler">通过用户名称获得角色集合代理方法</param>
        /// <param name="urlAuthHandler">通过请求地址获得角色集合代理方法</param>
        /// <param name="appAuthHandler">通过用户名称获得应用程序集合代理方法</param>
        public static IApplicationBuilder UseBootstrapAdminAuthentication(this IApplicationBuilder builder, string appId, Func<string, IEnumerable<string>> userNameAuthHandler, Func<string, IEnumerable<string>> urlAuthHandler, Func<string, IEnumerable<string>> appAuthHandler)
        {
            AppId = appId;

            builder.UseAuthentication();

            builder.Use(async (context, next) =>
            {
                // 客户端保持了 Cookie 服务器端无此用户
                if (context.User.Identity.IsAuthenticated && (string.IsNullOrEmpty(context.User.Identity.Name) || !userNameAuthHandler(context.User.Identity.Name).Any()))
                {
                    await context.SignOutAsync();
                    await context.ChallengeAsync();
                    return;
                }
                await next();
            });

            // 增加模拟用户中间件
            builder.UseBootstrapAdminSimulate();

            // Web api Validate
            builder.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), app => app.Use(async (context, next) =>
            {
                if (!context.User.Identity.IsAuthenticated)
                {
                    JwtAuthentication(context);
                }

                if (context.User.Identity.IsAuthenticated)
                {
                    AddRoles(context.User, RetrieveRolesByUserName(context.User.Identity.Name), new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme));
                }
                await next();
            }));

            _urlAuthHandler = urlAuthHandler;
            _appAuthHandler = appAuthHandler;
            _userAuthHandler = userNameAuthHandler;
            return builder;
        }

        /// <summary>
        /// 通过指定用户名获取授权角色集合
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        internal static IEnumerable<string> RetrieveRolesByUserName(string userName) => _userAuthHandler(userName);

        /// <summary>
        /// 通过指定访问地址获取授权角色集合
        /// </summary>
        /// <param name="url">指定地址</param>
        /// <returns>角色集合</returns>
        internal static IEnumerable<string> RetrieveRolesByUrl(string url) => _urlAuthHandler(url);

        /// <summary>
        /// 通过指定用户名获取授权 App 集合
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <returns>应用程序集合</returns>
        internal static IEnumerable<string> RetrieveAppsByUserName(string userName) => _appAuthHandler(userName);

        /// <summary>
        /// 获得 当前应用程序标识
        /// </summary>
        internal static string AppId { get; private set; }

        /// <summary>
        /// 添加 Claim 到当前用户实例中
        /// </summary>
        /// <param name="user"></param>
        /// <param name="roles"></param>
        /// <param name="identity"></param>
        internal static void AddRoles(ClaimsPrincipal user, IEnumerable<string> roles, ClaimsIdentity identity)
        {
            roles?.ToList().ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
            user.AddIdentity(identity);
        }

        private static void JwtAuthentication(HttpContext context)
        {
            // Jwtbeare authorization
            var token = context.Request.Headers["Authorization"].LastOrDefault();
            if (!string.IsNullOrEmpty(token) && token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            {
                token = token.Substring(7);

                // validate token
                var tokenOption = context.RequestServices.GetRequiredService<IConfiguration>().GetOption(() => new TokenValidateOption());
                var tokenHandler = new JwtSecurityTokenHandler();
                context.User = tokenHandler.ValidateToken(token, new TokenValidationParameters()
                {
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOption.SecurityKey)),
                    ValidIssuer = tokenOption.Issuer,
                    ValidAudience = tokenOption.Audience
                }, out var securityToken);
            }
        }
    }
}
